package com.faya.green.abnormal;

import com.faya.green.enumeration.ResultEnum;
import com.faya.green.enumeration.StatusEnum;
import com.faya.green.enumeration.SysLogEnum;
import com.faya.green.model.ResultJson;
import com.faya.green.model.SysLog;
import com.faya.green.model.User;
import com.faya.green.service.SysLogService;
import com.faya.green.utils.CommonUtils;
import com.google.gson.Gson;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.HostUnauthorizedException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.security.auth.Subject;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;

/**
 * 用户 ：LX
 * 创建时间： 2018/7/6. 13:56
 * 地点：广州
 * 目的: 全局异常处理类
 *      当controller发生异常的时候，这个类会对指定的异常进行捕获，不让异常抛出到用户界面
 * 结果：
 * springboot提供了一个默认映射 /error，当处理中抛出异常之后，会转到该请求中处理，并且该请求有一个全局的错误页面用来展示异常内容
 *  * 也可以自己创建全局异常处理类，通过@ControllerAdvice定义统一异常处理类，@ExceptionHandler 用来定义函数针对的异常类型，最后将Exception对象和请求URL映射到error.html中
     异常处理后返回可以有两种方式：
         返回异常提示页面：如将Exception对象和请求URL映射到 resource/templates/error.html中，可以有多个错误页面。
         返回json格式异常信息：可以创建一个对象，用于封装json数据
 */
//定义统一的异常处理类，可以不用在每个Controller中逐个定义异常处理方式,基于@ControllerAdvice注解的全局异常统一处理只能针对于Controller层的异常，
// 意思是只能捕获到Controller层的异常，在service层或者其他层面的异常都不能捕获.如果Controller层对异常进行了catch处理，那么在这里就不会捕获到Controller层的异常了
@ControllerAdvice
public class GlobalExceptionHandler {

    private static org.slf4j.Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @Autowired
    private SysLogService sysLogService;

    /**
     * 默认错误页面
     */
    private static final String DEFAUL_ERROR_VIEW = "/common/error";

    /**
     * 无权限访问页面
     */
    private static final String NO_AUTHOR_VIEW = "/common/noAuthorized";


    /**
     * 默认异常处理方法
     * @param request
     * @param e 错误
     * @return 返回异常请求路径和异常信息
     */
    @ExceptionHandler(value = Exception.class) //用来定义函数针对的异常类型，controller通过抛出的异常类型匹配
    public ModelAndView defaulErrorHandler(HttpServletRequest request, Exception e){
        ModelAndView mav = new ModelAndView();
        //异常信息
        mav.addObject("msg",e);
        //异常请求路径
        mav.addObject("url","请求路径：" + request.getRequestURI());

        log.error("错误信息:" + e );
        log.error("请求路径：" + request.getRequestURI());
        mav.setViewName(DEFAUL_ERROR_VIEW);

        //日志记录
        org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
        User useUser = (User)subject.getPrincipal();

        SysLog sysLog = new SysLog();
        sysLog.setId(UUID.randomUUID().toString());
        sysLog.setType(SysLogEnum.TYPE_ERROR.getCode());
        sysLog.setCount("错误：" + useUser.getName() + " 抛出异常，错误信息:" + e + " ，错误路径" + request.getRequestURI());
        sysLog.setCreateTime(CommonUtils.getTimestamp());
        sysLog.setUserId(useUser.getId());
        sysLogService.addSysLog(sysLog);

        return mav;
    }

    /**
     * 要返回指定异常未json字符串的时候，用下面这个方法即可
     * 对自定义的controller异常进行捕获处理，不让他直接展示到前台，会返回json数据，
     * 如果返回的是json格式，需要加上@ResponsBody
     */
    @ExceptionHandler(value = ControllerJsonException.class) //@ExceptionHandler 匹配抛出自定义的异常类型 ControllerException
    @ResponseBody
    public ResultJson jsonErrorHandler(HttpServletRequest request,ControllerJsonException e) throws Exception{

        Map map = new HashMap();
        map.put("msg", e.getMsg());
        map.put("url", request.getRequestURI());

        log.error("自定义异常错误信息:" + e );
        log.error("异常请求路径：" + request.getRequestURI());

        //日志记录
        org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
        User useUser = (User)subject.getPrincipal();

        SysLog sysLog = new SysLog();
        sysLog.setId(UUID.randomUUID().toString());
        sysLog.setType(SysLogEnum.TYPE_ERROR.getCode());
        sysLog.setCount("错误：用户" + useUser.getName() + " 抛出异常，错误信息:" + e + " ，错误路径" + request.getRequestURI());
        sysLog.setCreateTime(CommonUtils.getTimestamp());
        sysLog.setUserId(useUser.getId());
        sysLogService.addSysLog(sysLog);

        return new ResultJson(null, ResultEnum.RESULT_ERROR.getStatus(), e.getMsg(), null);
    }


    /**
     * 对自定义的controller异常进行捕获处理，返回页面
     */
    @ExceptionHandler(value = ControllerException.class) //@ExceptionHandler 匹配抛出自定义的异常类型 ControllerException
    public ModelAndView errorHandler(HttpServletRequest request,ControllerException e) throws Exception{

        log.error("自定义异常错误信息:" + e + " ，请求路径：" + request.getRequestURI());

        ModelAndView mav = new ModelAndView();
        //异常信息
        mav.addObject("msg",e);
        //异常请求路径
        mav.addObject("url","请求路径：" + request.getRequestURI());
        mav.setViewName(DEFAUL_ERROR_VIEW);

        //日志记录
        org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
        User useUser = (User)subject.getPrincipal();

        SysLog sysLog = new SysLog();
        sysLog.setId(UUID.randomUUID().toString());
        sysLog.setType(SysLogEnum.TYPE_ERROR.getCode());
        sysLog.setCount("错误：用户 " + useUser.getName() + " 发生异常，错误信息:" + e + " ，错误路径" + request.getRequestURI());
        sysLog.setCreateTime(CommonUtils.getTimestamp());
        sysLog.setUserId(useUser.getId());
        sysLogService.addSysLog(sysLog);

        return mav;
    }

    /**
     * shiro异常处理
     * @param request
     * @param shiroError
     * @return
     */
    @ExceptionHandler(value = ShiroException.class)
    public ModelAndView authorError(HttpServletRequest request, ShiroException shiroError){
        ModelAndView mv = new ModelAndView();
        log.error("权限异常:" + shiroError);
        log.error("请求路径:" + request.getRequestURI());

        if (shiroError instanceof UnsupportedTokenException){
            mv.addObject("msg", " 身份令牌异常，不支持的身份令牌");
        } else if (shiroError instanceof UnknownAccountException){
            mv.addObject("msg", " 未知账户/没找到帐号,登录失败");
        } else if (shiroError instanceof ExpiredCredentialsException){
            mv.addObject("msg", " 过期的凭据，请重新登录");
        } else if (shiroError instanceof IncorrectCredentialsException){
            mv.addObject("msg", " 错误的凭据");
        } else if (shiroError instanceof CredentialsException){
            mv.addObject("msg", " 凭据异常");
        } else if (shiroError instanceof AuthenticationException){
            mv.addObject("msg", " 凭据异常");
        } else if (shiroError instanceof HostUnauthorizedException){
            mv.addObject("msg", " 没有访问权限，访问异常");
        } else if (shiroError instanceof UnauthorizedException){
            mv.addObject("msg", " 没有访问权限，访问异常");
        } else if (shiroError instanceof UnauthenticatedException){
            mv.addObject("msg", " 授权异常");
        } else if (shiroError instanceof AuthorizationException){
            mv.addObject("msg", " 授权异常 ");
        } else {
            mv.addObject("msg", " 权限异常 ");
        }

        mv.addObject("url",  "非法请求路径:" + request.getRequestURI());
        mv.setViewName(NO_AUTHOR_VIEW);

        //日志记录
        org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
        User useUser = (User)subject.getPrincipal();

        SysLog sysLog = new SysLog();
        sysLog.setId(UUID.randomUUID().toString());
        sysLog.setType(SysLogEnum.TYPE_ERROR.getCode());
        sysLog.setCount("错误：用户 " + useUser.getName() + " 未经授权的访问，请求路径:" + request.getRequestURI());
        sysLog.setCreateTime(CommonUtils.getTimestamp());
        sysLog.setUserId(useUser.getId());
        sysLogService.addSysLog(sysLog);

        return mv;
    }
}
